iT邦幫忙

2024 iThome 鐵人賽

0
Kubernetes

都什麼年代了,還在學 Kubernetes系列 第 37

學 Kubernetes 的第三十七天 - Security - RBAC

  • 分享至 

  • xImage
  •  

RBAC (Role-Based Access Control) 基於角色的存取控制 (RBAC) 是一種控制資源存取的方法。使用者透過角色與作為安全目標的系統資源相關聯,並且使用者和系統資源之間的關係用於確定是否允許存取。

https://ithelp.ithome.com.tw/upload/images/20241021/20168212caGRvW0Lus.png

我們可以從公司制度理解這個概念:身為一個外人 (User),在公司是沒有任何權利,寸步難行。一旦你進入公司 (Binding) 成為管理人員職位 (Role),你便獲得了這個職位的權利,比如員工資料的查詢、修改權限等等。

RBAC 通過提供精細的訪問控制、簡化權限管理、提高安全性、支持多租戶環境、增強合規性和審計能力、提供靈活性和可擴展性,以及減少錯誤和提高運營效率,為系統的安全性和管理帶來了顯著的好處。

Kubernetes 中的 RBAC

https://ithelp.ithome.com.tw/upload/images/20241021/20168212xRhmizc9Kt.png

在 Kubernetes 裡也有 RBAC 方法,RBAC API 聲明了四種 Kubernetes 對象:

  1. Role:定義了一組可以對特定資源執行的操作。Role 只能應用於 Namespace 範圍內的資源。
  2. ClusterRole:類似於 Role,但它可以應用於整個集群範圍內的資源。
  3. RoleBinding:將一個 Role 與一個或多個用戶、群組或服務帳戶綁定起來,從而授予這些實體在特定 Namespace 中的權限。
  4. ClusterRoleBinding:將一個 ClusterRole 與一個或多個用戶、群組或服務帳戶綁定起來,從而授予這些實體在整個集群範圍內的權限。

Role 和 ClusterRole

RBAC 的 Role 或 ClusterRole 中包含一組代表相關權限的規則。 這些權限是純粹累加的(不存在拒絕某操作的規則)。

Role 與 ClusterRole 很相似,但 Role 只能用來定義特定 namespace 內的資源操作,而 ClusterRole 則可以定義整個集群範圍內的資源操作。簡單來說:

  • Role
    • 只能定義 Namespace-scoped 的資源,如 Pod
    • 建立 Role 時,你必須指定該 Role 所屬的 namespace
    • 如果你希望在 namespace 內定義角色,應該使用 Role
  • ClusterRole
    • 能夠定義 Cluster-scoped 和 Namespace-scoped 的資源
    • 建立 ClusterRole 時,你不用(也不能)指定 namespace
    • 如果你希望定義叢集範圍的角色,應該使用 ClusterRole

查詢資源做用於 namespace 還是 Cluster,可以用以下指令查詢:

# 做用於 Namespace-scoped 的資源
kubectl api-resources --namespaced
# 做用於 Cluster-scoped 的資源
kubectl api-resources --namespaced=false

Role 示例

這是一個位於 default namespace 的 Role 的示例,可用來授予對 Pod 的讀存取權:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""] # "" 標明 core API 組
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

ClusterRole 示例

這是一個 ClusterRole 的示例,可用來授予對 Secret 的讀存取權:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  # "namespace" 被忽略,因為 ClusterRoles 不受 namespace 限制
  name: secret-reader
rules:
- apiGroups: [""]
  # 在 HTTP 層面,用來訪問 Secret 資源的名稱為 "secrets"
  resources: ["secrets"]
  verbs: ["get", "watch", "list"]

不過上面我們也提到過,ClusterRole 創建的讀存權限,實際上也可以授予給特定 namespace,這取決於我們使用 RoleBinding 還是 ClusterRoleBinding

RoleBinding 和 ClusterRoleBinding

角色绑定(Role Binding)是將角色中定義的權限賦予一個或者一組使用者。 它包含若干主體(Subject)(使用者、組或服務帳戶)的列表和對這些主體所獲得的角色的引用。 RoleBinding 在指定的 namespace 中執行授權,而 ClusterRoleBinding 在叢集範圍執行授權。

一個 RoleBinding 可以引用同一的 namespace 中的任何 Role。 或者,一個 RoleBinding 可以引用某 ClusterRole 並將該 ClusterRole 繫結到 RoleBinding 所在的 namespace 。 如果你希望將某 ClusterRole 繫結到叢集中所有 namespace,你要使用 ClusterRoleBinding。

用表格將 RoleBindingClusterRoleBinding 的內容整理如下:

特性 RoleBinding ClusterRoleBinding
角色定義的權限賦予 將角色中定義的權限賦予一個或者一組使用者 同左
主體(Subject)列表 包含使用者、組或服務帳戶 同左
授權範圍 在指定的 namespace 中執行授權 在叢集範圍內執行授權
引用 Role 可以引用同一 namespace 中的任何 Role 不適用
引用 ClusterRole 可以引用 ClusterRole 並繫結到 RoleBinding 所在的 namespace 繫結 ClusterRole 到叢集中所有 namespace

RoleBinding 示例

下面的例子中的 RoleBinding 將 pod-reader Role 授予在 default namespace 中的使用者 jane。 這樣,使用者 jane 就具有了讀取 default namespace 中所有 Pod 的權限。

apiVersion: rbac.authorization.k8s.io/v1
# 此角色繫結允許 "jane" 讀取 "default" namespace 中的 Pod
# 你需要在該 namespace 中有一個名為 “pod-reader” 的 Role
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
# 你可以指定不止一個“subject(主體)”
- kind: User
  name: jane # "name" 是區分大小寫的
  apiGroup: rbac.authorization.k8s.io
roleRef:
  # "roleRef" 指定與某 Role 或 ClusterRole 的繫結關係
  kind: Role        # 此欄位必須是 Role 或 ClusterRole
  name: pod-reader  # 此欄位必須與你要繫結的 Role 或 ClusterRole 的名稱匹配
  apiGroup: rbac.authorization.k8s.io

RoleBinding 也可以引用 ClusterRole,以將對應 ClusterRole 中定義的存取權授予 RoleBinding 所在 namespace 的資源。這種引用使得你可以跨整個叢集定義一組通用的角色, 之後在多個 namespace 中復用。

例如,儘管下面的 RoleBinding 引用的是一個 ClusterRole,dave 只能訪問 development namespace 中的 Secret 對象,因為 RoleBinding 所在的 namespace 是 development

apiVersion: rbac.authorization.k8s.io/v1
# 此角色繫結使得使用者 "dave" 能夠讀取 "development" namespace 中的 Secret
# 你需要一個名為 "secret-reader" 的 ClusterRole
kind: RoleBinding
metadata:
  name: read-secrets
  # RoleBinding 的 namespace 決定了存取權的授予範圍。
  # 這裡隱含授權僅在 "development" namespace 內的存取權。
  namespace: development
subjects:
- kind: User
  name: dave # 'name' 是區分大小寫的
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

ClusterRoleBinding 示例

要跨整個叢集完成存取權的授予,你可以使用一個 ClusterRoleBinding。 下面的 ClusterRoleBinding 允許 "manager" 組內的所有使用者訪問任何 namespace 中的 Secret。

apiVersion: rbac.authorization.k8s.io/v1
# 此叢集角色繫結允許 “manager” 組中的任何人訪問任何 namespace 中的 Secret 資源
kind: ClusterRoleBinding
metadata:
  name: read-secrets-global
subjects:
- kind: Group
  name: manager      # 'name' 是區分大小寫的
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

建立了繫結之後,你不能再修改繫結對象所引用的 Role 或 ClusterRole,也就是 roleRef 欄位。如果你想要改變現有繫結對象中 roleRef 欄位的內容,必須刪除重新建立繫結對象。

對資源的引用

大多數資源由代表其名字的字串表示,例如 pods,就像它們出現在相關 API endpoint 的 URL 中一樣。然而,有一些 Kubernetes API 還包含了"子資源",比如 pod 的 logs。在 Kubernetes 中,pod logs endpoint 的 URL 格式為:

GET /api/v1/namespaces/{namespace}/pods/{name}/log

在這種情況下,"pods" 是 namespace 資源,而 "log" 是 pods 的子資源。為了在 RBAC 中表示出這一點,我們需要使用斜線來劃分資源 與子資源。如果需要角色繫結主體讀取 pods 以及 pod log,需要這樣定義角色:

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  namespace: default
  name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list"]

另外還可以通過 resourceNames 欄位針對特定資源賦予權限。例如,如果需要限定一個角色繫結主體只能 "get" 或者 "update" 一個 configmap 時,您可以定義以下角色:

kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  namespace: default
  name: configmap-updater
rules:
- apiGroups: [""]
  resources: ["configmap"]
  resourceNames: ["my-configmap"]
  verbs: ["update", "get"]

值得注意的是,如果設定了 resourceNames,則請求所使用的動詞不能是 list、watch、create 或者 deletecollection。 由於資源名不會出現在 create、list、watch 和 deletecollection 等 API 請求的 URL 中。

實作: ServiceAccount with RBAC

  • 建立 Service Account
kubectl create serviceaccount demosa
  • 使用以下組態文件建立 Pod
apiVersion: v1
kind: Pod
metadata:
  name: client
spec:
   serviceAccount: demosa
   containers:
   - name: client
     image: nginx
  • 查看 Pod 的 Service Account Token
kubectl describe pod client

結果如下

Name:             client
Namespace:        default
Priority:         0
Service Account:  demosa
[...]
Containers:
  client:
[...]
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-pbz28 (ro)
[...]
  • 查看 Pod 裡的身份認證和授權內容
kubectl exec client -- ls -l /var/run/secrets/kubernetes.io/serviceaccount

結果如下

total 0
lrwxrwxrwx 1 root root 13 Aug  7 10:18 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root 16 Aug  7 10:18 namespace -> ..data/namespace
lrwxrwxrwx 1 root root 12 Aug  7 10:18 token -> ..data/token
  • ca.crt 是一個 CA 憑證,用於驗證 Kubernetes API 伺服器的 SSL/TLS 憑證。Pod 在與 Kubernetes API 進行安全通信時,可以使用這個憑證來確保通信的安全性和完整性。

  • namespace 文件包含了 Pod 所在的 Kubernetes namespace 的名稱。該名稱用於在 API 請求中識別 Pod 的作用範圍。

  • token 是一個 Bearer Token (JWT 格式),用於 Pod 與 Kubernetes API 伺服器進行身份驗證。當 Pod 需要與 Kubernetes API 互動(例如讀取 ConfigMap、Secrets,或查詢叢集狀態)時,這個 Token 會被用來進行身份認證。

  • 使用 JSON Web Tokens 解碼 token 內容,結果如下:

{
  "aud": [
    "https://kubernetes.default.svc.cluster.local"
  ],
  "exp": 1754561936,
  "iat": 1723025936,
  "iss": "https://kubernetes.default.svc.cluster.local",
  "jti": "32b625d5-40f1-4d2a-ba0f-88691886e6a9",
  "kubernetes.io": {
    "namespace": "default",
    "node": {
      "name": "wslkind-worker2",
      "uid": "012ff8ef-8a88-4289-8fc4-0751ea78030f"
    },
    "pod": {
      "name": "client",
      "uid": "15396187-91f6-4ccc-9634-a91d4daed87e"
    },
    "serviceaccount": {
      "name": "demosa",
      "uid": "4f4bbe18-f38f-42bc-b0ca-aa379fdc4a45"
    },
    "warnafter": 1723029543
  },
  "nbf": 1723025936,
  "sub": "system:serviceaccount:default:demosa"
}

ServiceAccount Authentication

  • 進入 Pod 容器
kubectl exec -it client -- sh
  • 取得並紀錄 token 和 ca 證書
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
  • 使用 ca 證書訪問 K8s api service
curl --cacert $CACERT -X GET https://kubernetes.default.svc.cluster.local/api

結果如下

{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "forbidden: User \"system:anonymous\" cannot get path \"/api\"",
  "reason": "Forbidden",
  "details": {},
  "code": 403
}

我們已經成功連線到 api service,不過我們沒通過驗證。

  • 加上 token,再訪問 K8s api service
curl --cacert $CACERT --header "Authorization: Bearer $TOKEN" -X GET https://kubernetes.default.svc.cluster.local/api

結果如下

{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "172.18.0.3:6443"
    }
  ]
}

我們已經成功通過驗證,取得回應。

雖然我們已經可以訪問 K8s api service,但我們沒有任何資源的操作權限,因為我們沒有綁定任何角色,這裡我們可以檢驗 Service Account 是否有權限對特定資源進行特定操作:

  • 透過 K8s api service 取得 Pod 列表
curl --cacert $CACERT --header "Authorization: Bearer $TOKEN" -X GET https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods

結果如下

{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "pods is forbidden: User \"system:serviceaccount:default:demosa\" cannot list resource \"pods\" in API group \"\" in the namespace \"default\"",
  "reason": "Forbidden",
  "details": {
    "kind": "pods"
  },
  "code": 403
}

另一種驗證方法:透過 kubectl auth can-i 指令:

kubectl auth can-i list pods --as=system:serviceaccount:default:demosa
---
no

RBAC

現在我們要將 demosa 跟 Role 綁定,使 demosa 有權限完成對特定資源的操作。

  • 使用以下指令建立 Role 和 Rolebinding
# 建立角色 demorole,授予可以 get, list 資源 pods 的權限
kubectl create role demorole --verb=get,list --resource=pods
# 建立角色關聯 demorolebinding,將角色 demorole 與服務帳號 demosa 關聯
kubectl create rolebinding demorolebinding --role=demorole --serviceaccount=default:demosa
  • 驗證 demosa 的操作權限
kubectl auth can-i list pods --as=system:serviceaccount:default:demosa
---
yes

我們試著從 Pod 內部直接訪問 api service。

  • 進入 Pod 容器
kubectl exec -it client -- sh
  • 取得並紀錄 token 和 ca 證書
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
  • 透過 K8s api service 取得 Pod 列表
curl --cacert $CACERT --header "Authorization: Bearer $TOKEN" -X GET https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods

結果如下

{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "resourceVersion": "737483"
  },
  "items": [...]
}

清理

kubectl delete rolebinding demorolebinding
kubectl delete role demorole
kubectl delete serviceaccount demosa

實作: User with RBAC

我們會在這一章節實作,如何從頭建立一個新的 Normal User,然後透過 RBAC 賦予使用者合適的權限管理叢集。

開始之前

我們需要為 Client Certificates 作為驗證手段,因此請在客戶端事先安裝 openssl

產生 User key 和 CSR

  • 產生使用者私鑰
openssl genrsa -out demouser.key 2048
  • 使用使用者私鑰產生憑證簽署請求 (CSR)。
openssl req -new -key demouser.key -out demouser.csr -subj "/CN=demouser/O=rd"

K8s 會查看 X.509 證書中的主體資訊 (Subject),將 CN (Common Name) 當作 UserName,O (Organization) 當作 Group。

接下來我們要使用 K8s 的 CA 憑證來簽署 CSR,有兩個做法。

簽署方法 1: 透過 CertificateSigning 資源

  • 使用 Base64 加密 CSR 文件
cat demouser.csr | base64 | tr -d "\n"

組態文件: demouser-csr.yaml

apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
    name: demouser
spec:
    groups:
    - system:authenticated
    # 將 Base64 加密過的 CSR 貼在這裡
    request: <CSR>
    signerName: kubernetes.io/kube-apiserver-client
    usages:
    - client auth
  • 建立 CertificateSigningRequest
kubectl apply -f demouser-csr.yaml
  • 查看 CertificateSigningRequest 詳細內容
kubectl describe certificatesigningrequests demouser
---
Name:         demouser
Labels:       <none>
Annotations:  <ignore>

CreationTimestamp:  Thu, 08 Aug 2024 02:05:29 +0800
Requesting User:    kubernetes-admin
Signer:             kubernetes.io/kube-apiserver-client
Status:             Pending
Subject:
         Common Name:    demouser
         Serial Number:
Events:  <none>

可以看到登記的使用者是 CN 定義的 demouser,建立請求者是 kubernetes-admin 這個我們正在使用的預設 User。
目前這個請求狀態還在 Pending 中,接下來要使用 kubectl 命令批准這個請求。

  • 批准 CertificateSigningRequest
kubectl certificate approve demouser
  • 再次查看 CSR 的詳細內容
kubectl describe certificatesigningrequests demouser
---
Name:         demouser
Labels:       <none>
Annotations:  <ignore>

CreationTimestamp:  Thu, 08 Aug 2024 02:05:29 +0800
Requesting User:    kubernetes-admin
Signer:             kubernetes.io/kube-apiserver-client
Status:             Approved,Issued
Subject:
         Common Name:    demouser
         Serial Number:
Events:  <none>

這樣我們就算完成了簽署。接下來我們要把使用者公鑰提取出來。

  • 取得 CSR 中的使用者公鑰,並將其用 Base64 解碼,輸出為 demouser.crt 檔案
kubectl get certificatesigningrequests demouser -o jsonpath='{.status.certificate}' | base64 --decode > demouser.crt
  • 查詢使用者公鑰的內容
openssl x509 -noout -in demouser.crt -text

結果如下

Certificate:
    Data:
	    [...]
        Issuer: CN = kubernetes
        Validity
            Not Before: Aug  7 21:14:41 2024 GMT
            Not After : Aug  7 21:14:41 2025 GMT
        Subject: CN = demouser
[...]

簽署方法2: 透過 openssl x509

不透過叢集簽署,我們需要取得叢集的 CA 私鑰和證書。

一般來說我們需要進入叢集任一節點中取得位於路徑 /etc/kubernetes/pki 下的 ca.crt, ca.key 檔案。但我們的叢集節點等於 docker 容器,可以簡單的透過 docker cp 指令下載檔案 。

  • 從 docker 節點容器中下載 ca.crtca.key 檔案
docker cp wslkind-control-plane:/etc/kubernetes/pki/ca.key ./ca.key
docker cp wslkind-control-plane:/etc/kubernetes/pki/ca.crt ./ca.crt

現在我們在本地的目錄應該會有以下 4 個檔案: ca.key, ca.crt, demouser.key, demouser.csr

  • 建立使用者公鑰 demouser.crt
openssl x509 -req -in demouser.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out demouser.crt -days 365

更新 kubeconfig file

有了使用者證書後,接下來要在 kubeconfig 建立對應的 UserContext,才能在 kubectl 使用。

  • 建立 User
kubectl config set-credentials demouser \
    --client-certificate=demouser.crt \
    --client-key=demouser.key \
    --embed-certs=true
  • 檢查叢集的使用者清單
kubectl config get-users
---
NAME
demouser
kind-wslkind
  • 查詢當前叢集的名稱
kubectl config view --minify -o jsonpath="{.clusters[0].name}"
---
kind-wslkind
  • 建立 Context
kubectl config set-context demo --user=demouser --cluster=kind-wslkind
  • 檢查當前 Context 列表
kubectl config get-contexts
---
CURRENT   NAME              CLUSTER           AUTHINFO          NAMESPACE
          demo              kind-wslkind      demouser
*         kind-wslkind      kind-wslkind      kind-wslkind

儘管我們成功的建立 User 和 Context,但我們並沒有賦予任何權限給 demouser,因此我們只能訪問叢集,而無法進行任何資源。我們可以測試一下:

  • 切換 demo context
kubectl config use-context demo
  • 取得叢集 node 列表
kubectl get node
---
Error from server (Forbidden): nodes is forbidden: User "demouser" cannot list resource "nodes" in API group "" at the cluster scope

RBAC

現在我們要為 demouser 加上權限,使它有權限完成對資源的操作

  • 切換回預設 context
kubectl config use-context kind-wslkind
  • 建立 Role 和 Binding。
# 建立允許只讀 pod 權限的叢集角色,用 rolebinding 將角色與使用者綁定,並指定 namespace kube-system
kubectl create clusterrole pod-viewer --verb=get,list --resource=pods
kubectl create rolebinding demouser-pod-viewer-binding --clusterrole=pod-viewer --user=demouser --namespace=kube-system
---
# 建立允許只讀 node 權限的叢集角色,用 clusterrolebinding 將角色與使用者綁定
kubectl create clusterrole node-viewer --verb=get,list --resource=nodes
kubectl create clusterrolebinding demouser-node-viewer-binding --clusterrole=node-viewer --user=demouser
---
# 建立允許只讀 clusterroles 權限的叢集角色,用 clusterrolebinding 將角色與 Group rd 綁定
kubectl create clusterrole node-viewer --verb=get,list --resource=clusterroles
kubectl create clusterrolebinding demouser-node-viewer-binding --clusterrole=node-viewer --group=rd
  • 驗證權限
kubectl auth can-i get pods -n default --as=demouser
no
---
kubectl auth can-i get pods -n kube-system --as=demouser
yes
---
kubectl auth can-i get nodes --all-namespaces --as=demouser
yes
---
kubectl auth can-i get clusterroles --all-namespaces --as=demouser
no
---
kubectl auth can-i get clusterroles --all-namespaces --as=whatever --as-group=rd
yes

清理

kubectl config use-context kind-wslkind
kubectl delete clusterrolebinding demouser-node-viewer-binding
kubectl delete rolebinding demouser-pod-viewer-binding -n kube-system
kubectl delete clusterrole pod-viewer node-viewer
kubectl config unset contexts.demo
kubectl config unset users.demouser

小結

到今天為止,本系列教學的本篇算是告一段落。感謝各位讀者一路陪伴完成這系列的 Kubernetes 教學。希望大家在學習後,能夠勇敢地嘗試使用這些工具,解決實際問題,並將所學延伸到更廣的應用場景中。

Kubernetes 是一個充滿彈性和可能性的工具,只有透過實踐和探索,才能真正掌握它的力量。如果你在實作過程中有遇到挑戰,請不要害怕去尋找解決方案,這也是成長的重要一環。

未來我有新的心得或發現,也會以番外篇的形式,在部落格持續更新,與大家分享。希望我們能一同進步、探索更多可能性!

歡迎大家來我的部落格看看其他文章:Vincent's Blog


參考


上一篇
學 Kubernetes 的第三十六天 - Security - Kubernetes API
系列文
都什麼年代了,還在學 Kubernetes37
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言